package com.uinnova.product.eam.service.impl;

import com.alibaba.fastjson.JSONArray;
import com.binary.core.exception.BinaryException;
import com.binary.core.util.BinaryUtils;
import com.binary.framework.web.RemoteResult;
import com.uinnova.product.eam.base.enums.ResultCodeEnum;
import com.uinnova.product.eam.base.exception.ServerException;
import com.uinnova.product.eam.base.util.EamUtil;
import com.uinnova.product.eam.comm.model.es.AppSquareConfig;
import com.uinnova.product.eam.comm.model.es.AssetChangeRecord;
import com.uinnova.product.eam.feign.workable.FlowableFeign;
import com.uinnova.product.eam.feign.workable.entity.*;
import com.uinnova.product.eam.model.constants.FlowableConstant;
import com.uinnova.product.eam.model.enums.AppSquareTypeEnum;
import com.uinnova.product.eam.service.ICISwitchSvc;
import com.uinnova.product.eam.service.asset.AssetChangeRecordSvc;
import com.uinnova.product.eam.service.asset.AssetContent;
import com.uinnova.product.eam.service.es.AppSquareConfigDao;
import com.uinnova.product.eam.service.es.IamsESCIPrivateSvc;
import com.uinnova.product.eam.service.flowable.FlowableApprovalSvc;
import com.uinnova.product.eam.vo.AssertApproveInfo;
import com.uinnova.product.eam.vo.GTApproveRecord;
import com.uinnova.product.eam.web.asset.peer.ArchitectureAssetPeer;
import com.uinnova.product.vmdb.comm.model.ci.CCcCi;
import com.uinnova.product.vmdb.comm.model.ci.CcCi;
import com.uinnova.product.vmdb.provider.ci.bean.CcCiInfo;
import com.uinnova.project.service.threadlocal.ThreadVariable;
import com.uino.api.client.permission.IUserApiSvc;
import com.uino.bean.cmdb.base.ESCIInfo;
import com.uino.bean.cmdb.base.LibType;
import com.uino.bean.cmdb.base.SaveBatchCIContext;
import com.uino.bean.permission.base.SysUser;
import com.uino.bean.permission.business.UserInfo;
import com.uino.bean.permission.query.CSysUser;
import com.uino.util.sys.SysUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

@Slf4j
@Component(value = FlowableConstant.GT_ASSERT_APPROVE)
public class GTAssertApprovalSvcImpl implements FlowableApprovalSvc {

    @Autowired
    private FlowableFeign flowableFeign;
    @Autowired
    private IUserApiSvc userApiSvc;
    @Autowired
    private ICISwitchSvc ciSwitchSvc;
    @Autowired
    private AssetChangeRecordSvc changeRecordSvc;
    @Autowired
    private ArchitectureAssetPeer architectureAssetPeer;
    @Autowired
    private IamsESCIPrivateSvc ciPrivateSvc;
    @Autowired
    private AppSquareConfigDao appSquareConfigDao;

    @Override
    public List<String> getApprovalUser(String businessKey, String taskKey) {
        return null;
    }

    @Override
    public List<String> countersignChildUser(String businessKey, String taskKey) {
        return null;
    }

    @Override
    public void reject(String businessKey) {
        //资产状态更新为“审批中-可编辑”
        PorcessResponse porcess = getProcess(businessKey);

        CSysUser cSysUser = new CSysUser();
        cSysUser.setLoginCodeEqual(porcess.getProcessStartUserId());
        List<SysUser> sysUserByCdt = userApiSvc.getSysUserByCdt(cSysUser);
        ThreadVariable.setSysUser(sysUserByCdt.get(0));

        CCcCi ciCdt = new CCcCi();
        ciCdt.setOwnerCodeEqual(porcess.getProcessStartUserId());
        ciCdt.setId(Long.valueOf(businessKey));
        List<CcCiInfo> ciInfos = ciSwitchSvc.queryCiInfoList(1L, ciCdt, null, false, true, LibType.PRIVATE);
        if (CollectionUtils.isEmpty(ciInfos)) {
            log.error("未找到资产信息：businessKey:{}, ownerCode:{}", businessKey, porcess.getProcessStartUserId());
            throw new BinaryException("未找到资产信息");
        }

        String dateTime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now());
        CcCiInfo ciInfo = ciInfos.get(0);
        ciInfo.getAttrs().put(AssetContent.RELEASE_STATE, AssetContent.APPROVE_EDIT);
        ciInfo.getAttrs().put(AssetContent.MODIFI_TIME, dateTime);
        saveCiInfo(ciInfo, LibType.PRIVATE);
    }

    @Override
    public void childReject(String childBusinessKey) {

    }

    @Override
    public void pass(String businessKey) {
        //资产状态更新为“已发布”
        PorcessResponse porcess = getProcess(businessKey);

        CSysUser cSysUser = new CSysUser();
        cSysUser.setLoginCodeEqual(porcess.getProcessStartUserId());
        List<SysUser> sysUserByCdt = userApiSvc.getSysUserByCdt(cSysUser);
        ThreadVariable.setSysUser(sysUserByCdt.get(0));

        CCcCi ciCdt = new CCcCi();
        ciCdt.setOwnerCodeEqual(porcess.getProcessStartUserId());
        ciCdt.setId(Long.valueOf(businessKey));
        List<CcCiInfo> ciInfos = ciSwitchSvc.queryCiInfoList(1L, ciCdt, null, false, true, LibType.PRIVATE);
        if (CollectionUtils.isEmpty(ciInfos)) {
            log.error("未找到资产信息：businessKey:{}, ownerCode:{}", businessKey, porcess.getProcessStartUserId());
            throw new BinaryException("未找到资产信息");
        }

        String dateTime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now());
        CcCiInfo ciInfo = ciInfos.get(0);
        ciInfo.getAttrs().put(AssetContent.RELEASE_STATE, AssetContent.RELEASE);
        ciInfo.getAttrs().put(AssetContent.MODIFI_TIME, dateTime);
        ciInfo.getAttrs().put(AssetContent.RELEASE_TIME, dateTime);

        CCcCi designCiCdt = new CCcCi();
        designCiCdt.setCiCodeEqual(ciInfo.getCi().getCiCode());
        List<CcCiInfo> designCiList = ciSwitchSvc.queryCiInfoList(1L, ciCdt, null, false, true, LibType.DESIGN);
        if (CollectionUtils.isEmpty(designCiList)) {
            ciInfo.getCi().setId(null);
        } else {
            ciInfo.getCi().setId(designCiList.get(0).getCi().getId());
        }
        try {
            CcCiInfo dbCiInfo = saveCiInfo(ciInfo, LibType.DESIGN);
            if (dbCiInfo != null) {
                updatePrivateCi(dbCiInfo);
            }
        } catch (BinaryException e) {
            if (e.getMessage().contains("[设计库]CI已存在,主键冲突:")) {
                String message = e.getMessage();
                throw new BinaryException("【" + message.substring(message.indexOf(":") + 2, message.length() - 1) + "】已存在");
            } else {
                throw e;
            }
        }

        //“变更记录”内，新增发布记录
        AssetChangeRecord changeRecord = new AssetChangeRecord();
        changeRecord.setState(2);
        changeRecord.setType(AssetContent.RELEASING);
        changeRecord.setCiCode(ciInfo.getCi().getCiCode());
        changeRecordSvc.saveChangeRecord(changeRecord, porcess.getProcessStartUserId());
    }

    private void updatePrivateCi(CcCiInfo ciInfoById) {
        CCcCi ciCdt = new CCcCi();
        ciCdt.setClassId(ciInfoById.getCi().getClassId());
        ciCdt.setCiCodeEqual(ciInfoById.getCi().getCiCode());
        List<CcCiInfo> privateList = ciSwitchSvc.queryCiInfoList(1L, ciCdt, null, false, true, LibType.PRIVATE);
        if (!CollectionUtils.isEmpty(privateList)) {
            for (CcCiInfo ccCiInfo : privateList) {
                ccCiInfo.setAttrs(ciInfoById.getAttrs());
            }
            log.info("=========更新私有库数据: {}", privateList.size());
            // 同步私有库数据
            if (!CollectionUtils.isEmpty(privateList)) {
                ciPrivateSvc.saveOrUpdateCIBatch(privateList, false);
            }
        } else {
            // 保存发布数据到私有库
            CcCi ciPrivate = new CcCi();
            BeanUtils.copyProperties(ciInfoById.getCi(),ciPrivate);
            ciPrivate.setId(null);

            CcCiInfo ciInfoPrivate = new CcCiInfo();
            BeanUtils.copyProperties(ciInfoById,ciInfoPrivate);
            ciInfoPrivate.setCi(ciPrivate);
            saveCiInfo(ciInfoPrivate, LibType.PRIVATE);
        }
    }

    @Override
    public void cancel(String businessKey) {
        //资产状态更新为“草稿”
        PorcessResponse porcess = getProcess(businessKey);

        CSysUser cSysUser = new CSysUser();
        cSysUser.setLoginCodeEqual(porcess.getProcessStartUserId());
        List<SysUser> sysUserByCdt = userApiSvc.getSysUserByCdt(cSysUser);
        ThreadVariable.setSysUser(sysUserByCdt.get(0));

        CCcCi ciCdt = new CCcCi();
        ciCdt.setOwnerCodeEqual(porcess.getProcessStartUserId());
        ciCdt.setId(Long.valueOf(businessKey));
        List<CcCiInfo> ciInfos = ciSwitchSvc.queryCiInfoList(1L, ciCdt, null, false, true, LibType.PRIVATE);
        if (CollectionUtils.isEmpty(ciInfos)) {
            log.error("未找到资产信息：businessKey:{}, ownerCode:{}", businessKey, porcess.getProcessStartUserId());
            throw new BinaryException("未找到资产信息");
        }

        String dateTime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now());
        CcCiInfo ciInfo = ciInfos.get(0);
        ciInfo.getAttrs().put(AssetContent.RELEASE_STATE, AssetContent.DRAFT);
        ciInfo.getAttrs().put(AssetContent.MODIFI_TIME, dateTime);
        saveCiInfo(ciInfo, LibType.PRIVATE);
    }

    /**
     * 根据业务定义key (视图ID) 获取当前流程中的全局变量
     *
     * @param businessKey 业务定义key (视图ID)
     * @return 流程全局变量
     */
    private PorcessResponse getProcess(String businessKey) {
        // 获取审批流程信息
        PorcessResponse processInstance = flowableFeign.getProcessInstanceByBusinessIdAndProcessDefinitionKey(businessKey, FlowableConstant.GT_ASSERT_APPROVE);
        if (BinaryUtils.isEmpty(processInstance) || StringUtils.isBlank(processInstance.getProcessInstanceId())) {
            throw new ServerException("流程异常");
        }
        return processInstance;
    }

    public AssertApproveInfo assertApproveInfo(String processInstanceId, String businessKey, String taskId) {
        if (StringUtils.isBlank(processInstanceId) || StringUtils.isBlank(businessKey) || StringUtils.isBlank(taskId)) {
            throw new BinaryException("缺少参数");
        }
        PorcessResponse porcess = flowableFeign.getProcessInstanceByProcessInstanceId(processInstanceId);
        if (porcess == null || StringUtils.isBlank(porcess.getProcessInstanceId())) {
            throw new BinaryException("未找到当前流程");
        }
        CCcCi ciCdt = new CCcCi();
        ciCdt.setOwnerCodeEqual(porcess.getProcessStartUserId());
        ciCdt.setId(Long.valueOf(businessKey));
        List<CcCiInfo> ciInfos = ciSwitchSvc.queryCiInfoList(1L, ciCdt, null, false, true, LibType.PRIVATE);
        if (CollectionUtils.isEmpty(ciInfos)) {
            log.error("未找到资产信息：businessKey:{}, ownerCode:{}", businessKey, porcess.getProcessStartUserId());
            throw new BinaryException("未找到资产信息");
        }
        TaskResponse taskInfo = flowableFeign.getTaskInfoByTaskId(taskId);
        if (taskInfo == null || StringUtils.isBlank(taskInfo.getProcessInstanceId())) {
            throw new BinaryException("未找到当前任务");
        }
        CcCiInfo ciInfo = ciInfos.get(0);
        AssertApproveInfo approveInfo = new AssertApproveInfo();
        approveInfo.setTaskEnd(taskInfo.getTaskEndTime() != null);
        approveInfo.setProcessEnd(!FLOWSTATUS.ACTIVE.equals(porcess.getStatus()));
        List<String> ciLabels = JSONArray.parseArray(ciInfo.getCi().getCiLabel(), String.class);
        approveInfo.setAssertName(String.join(",", ciLabels));
        approveInfo.setId(ciInfo.getCi().getId());
        approveInfo.setCiCode(ciInfo.getCi().getCiCode());
        approveInfo.setCiClassCode(ciInfo.getCiClass().getClassCode());
        approveInfo.setCiClassName(ciInfo.getCiClass().getClassName());


        CSysUser cdt = new CSysUser();
        cdt.setLoginCodeEqual(porcess.getProcessStartUserId());
        List<UserInfo> userInfos = userApiSvc.getUserInfoByCdt(cdt, true);
        approveInfo.setSubmitter(porcess.getProcessStartUserId());
        approveInfo.setSubmitterName(CollectionUtils.isEmpty(userInfos) ? porcess.getProcessStartUserId() : userInfos.get(0).getUserName());
        approveInfo.setSubmitTime(transDateFormat(porcess.getStartTime(), "yyyy/MM/dd HH:mm:ss"));
        Map<String, Object> routerVariables = porcess.getRouterVariables();
        if (!BinaryUtils.isEmpty(routerVariables)
                && routerVariables.containsKey("releaseDesc")
                && routerVariables.get("releaseDesc") != null
                && StringUtils.isNotBlank(routerVariables.get("releaseDesc").toString())) {
            approveInfo.setReleaseDesc(routerVariables.get("releaseDesc").toString());
        }
        setAppSquareAuth(approveInfo);
        return approveInfo;
    }

    /**
     * 设置资产卡片信息
     * @param approveInfo
     * @return
     */
    private void setAppSquareAuth(AssertApproveInfo approveInfo) {
        BoolQueryBuilder appSquarequery = QueryBuilders.boolQuery();
        appSquarequery.must(QueryBuilders.termQuery("classCode.keyword", approveInfo.getCiClassCode()));
        appSquarequery.must(QueryBuilders.termQuery("classification.keyword", AppSquareTypeEnum.ASSET.getType()));
        appSquarequery.must(QueryBuilders.termQuery("status", 1));
        AppSquareConfig systemAppSquareConfig = appSquareConfigDao.selectOne(appSquarequery);
        approveInfo.setHasCard(systemAppSquareConfig != null);
        if (systemAppSquareConfig != null) {
            approveInfo.setCardId(systemAppSquareConfig.getId());
            approveInfo.setCardName(systemAppSquareConfig.getCardName());
        }
    }

    private String transDateFormat(Date date, String format) {
        if (date == null) {
            return "-";
        }
        SimpleDateFormat sdf = new SimpleDateFormat(format);
        return sdf.format(date);
    }

    /**
     * 审批记录
     *
     * @param processInstanceId
     * @return
     */
    public GTApproveRecord approveRecords(String processInstanceId) {
        GTApproveRecord approveRecord = new GTApproveRecord();
        List<HistoryTaskResponse> tasks = new ArrayList<>();
        List<HistoryTaskResponse> histories = flowableFeign.getHistoryTaskByCurrentProcessInstanceId(processInstanceId);
        List<TaskResponse> actives = flowableFeign.getCurrentTaskByCurrentProcessInstanceId(processInstanceId);
        if (!BinaryUtils.isEmpty(histories)) {
            List<HistoryTaskResponse> historyTaskResponses = histories.stream().sorted(Comparator.comparing(HistoryTaskResponse::getCommitTime)).collect(Collectors.toList());
            extracted(historyTaskResponses);
            tasks.addAll(historyTaskResponses);
        }
        if (!CollectionUtils.isEmpty(actives)) {
            Set<String> assignees = new HashSet<>();
            Set<String> currentAssigneeLoginCodes = actives.stream().map(TaskResponse::getUserId).collect(Collectors.toSet());
            CSysUser cSysUser = new CSysUser();
            cSysUser.setLoginCodes(currentAssigneeLoginCodes.toArray(new String[currentAssigneeLoginCodes.size()]));
            List<SysUser> sysUserByCdt = userApiSvc.getSysUserByCdt(cSysUser);
            Map<String, SysUser> sysUserMap = sysUserByCdt.parallelStream()
                    .collect(Collectors.toMap(SysUser::getLoginCode, u -> u));
            for (TaskResponse taskResponse : actives) {
                HistoryTaskResponse historyTaskResponse = new HistoryTaskResponse();
                historyTaskResponse.setTaskId(taskResponse.getTaskId());
                historyTaskResponse.setTaskName(taskResponse.getTaskName());
                historyTaskResponse.setUserId(taskResponse.getUserId());
                historyTaskResponse.setProcessInstanceName(taskResponse.getProcessInstanceName());
                Map<String, Object> variables = new ConcurrentHashMap<>();
                variables.put("pass", "todo");
                variables.put("user", sysUserMap.containsKey(taskResponse.getUserId()) ? sysUserMap.get(taskResponse.getUserId()).getUserName() : taskResponse.getUserId());
                variables.put("icon", sysUserMap.containsKey(taskResponse.getUserId()) ? sysUserMap.get(taskResponse.getUserId()).getIcon() : "");
                historyTaskResponse.setVariables(variables);
                assignees.add(sysUserMap.containsKey(taskResponse.getUserId()) ? sysUserMap.get(taskResponse.getUserId()).getUserName() : taskResponse.getUserId());
                tasks.add(historyTaskResponse);
            }
            if (!CollectionUtils.isEmpty(assignees)) {
                approveRecord.setCurrentAssignees(assignees.stream().collect(Collectors.joining(",")));
            }
        }
        List<HistoryTaskResponse> results = EamUtil.copy(tasks, HistoryTaskResponse.class);
        approveRecord.setTasks(results);
        return approveRecord;
    }

    private void extracted(List<HistoryTaskResponse> historyTaskByCurrentProcessInstances) {
        List<String> userList = historyTaskByCurrentProcessInstances.stream().map(HistoryTaskResponse::getUserId).collect(Collectors.toList());
        HashSet<String> userIdSet = new HashSet<>(userList);
        String[] userIds = new String[userIdSet.size()];
        CSysUser cSysUser = new CSysUser();
        userIdSet.toArray(userIds);
        cSysUser.setLoginCodes(userIds);
        List<SysUser> sysUserByCdt = userApiSvc.getSysUserByCdt(cSysUser);
        Map<String, SysUser> sysUserMap = sysUserByCdt.parallelStream()
                .collect(Collectors.toMap(SysUser::getLoginCode, u -> u));
        for (HistoryTaskResponse historyTaskByCurrentProcessInstance : historyTaskByCurrentProcessInstances) {
            String userId = historyTaskByCurrentProcessInstance.getUserId();
            Map<String, Object> variables = historyTaskByCurrentProcessInstance.getVariables();
            variables.put("user", sysUserMap.containsKey(userId) ? sysUserMap.get(userId).getUserName() : userId);
            variables.put("icon", sysUserMap.containsKey(userId) ? sysUserMap.get(userId).getIcon() : "");
        }
    }

    /**
     * 流程驳回后重新提交
     * @param id
     * @return
     */
    public RemoteResult submitApprove(Long id) {
        CcCiInfo ciInfo = ciSwitchSvc.getCiInfoById(id, LibType.PRIVATE);
        if (!architectureAssetPeer.assertPublishNeedApprove(ciInfo.getCi().getClassId())) {
            throw new BinaryException("当前资产暂不支持评审");
        }
        Assert.notNull(ciInfo, "资产不存在");
        if (!AssetContent.APPROVE_EDIT.equals(ciInfo.getAttrs().get(AssetContent.RELEASE_STATE))) {
            throw new BinaryException("资产非审批中-可编辑状态，不允许提交审批");
        }
        architectureAssetPeer.checkAssert(ciInfo);
        TaskResponse task = flowableFeign.getCurrentUserActiveTask(id.toString(), FlowableConstant.GT_ASSERT_APPROVE, SysUtil.getCurrentUserInfo().getLoginCode());
        if (task == null || StringUtils.isBlank(task.getTaskId())) {
            throw new BinaryException("暂无任务数据");
        }
        if (!"rectification".equals(task.getDescription())) {
            throw new BinaryException("非退回至申请人节点");
        }
        // 查看当前任务节点是否已经完结
        Map<String, Object> taskEchoVariables = task.getTaskEchoVariables();
        if (!BinaryUtils.isEmpty(taskEchoVariables) && !BinaryUtils.isEmpty(taskEchoVariables.get("pass"))) {
            return new RemoteResult(Boolean.TRUE, ResultCodeEnum.REPEAT_OPERATE.getCode(), ResultCodeEnum.REPEAT_OPERATE.getMessage());
        }
        // 校验多审批用户同时操作流程情况
        Boolean existByTaskId = flowableFeign.isExistByTaskId(task.getTaskId());
        if (!existByTaskId) {
            return new RemoteResult(Boolean.TRUE, ResultCodeEnum.TASK_FAILURE.getCode(), ResultCodeEnum.TASK_FAILURE.getMessage());
        }

        TaskRequest taskRequest = new TaskRequest();
        taskRequest.setAction(FLOWACTION.ACCETP);
        taskRequest.setTaskId(task.getTaskId());
        flowableFeign.completeTask(taskRequest);

        String dateTime = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(LocalDateTime.now());
        //资产状态置成审批中
        ciInfo.getAttrs().put(AssetContent.RELEASE_STATE, AssetContent.APPROVE);
        ciInfo.getAttrs().put(AssetContent.MODIFI_TIME, dateTime);
        saveCiInfo(ciInfo, LibType.PRIVATE);
        return new RemoteResult(task.getTaskId());
    }

    private CcCiInfo saveCiInfo(CcCiInfo ciInfo, LibType libType) {
        ESCIInfo esciInfo = EamUtil.tranESCIInfo(ciInfo);
        Map<String, ? extends SaveBatchCIContext> result = ciSwitchSvc.saveOrUpdateBatchCI(
                Collections.singletonList(esciInfo),
                Collections.singletonList(esciInfo.getClassId()),
                esciInfo.getOwnerCode(), esciInfo.getOwnerCode(), libType);
        if (BinaryUtils.isEmpty(result) || !result.containsKey(esciInfo.getCiCode())) {
            return null;
        }
        ESCIInfo dbCIInfo = result.get(esciInfo.getCiCode()).getEsCi();
        return EamUtil.tranCcCiInfo(dbCIInfo, ciInfo.getAttrDefs(), ciInfo.getCiClass());
    }
}
